package com.mlming.springboot.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.mlming.springboot.consts.MallConst;
import com.mlming.springboot.dao.OrderItemMapper;
import com.mlming.springboot.dao.OrderMapper;
import com.mlming.springboot.dao.ProductMapper;
import com.mlming.springboot.dao.ShippingMapper;
import com.mlming.springboot.enums.PayStatusEnum;
import com.mlming.springboot.enums.PaymentTypeEnum;
import com.mlming.springboot.enums.ProductStatusEnum;
import com.mlming.springboot.enums.ResponseEnum;
import com.mlming.springboot.pojo.*;
import com.mlming.springboot.service.CartService;
import com.mlming.springboot.service.OrderService;
import com.mlming.springboot.vo.OrderItemVo;
import com.mlming.springboot.vo.OrderVo;
import com.mlming.springboot.vo.ResponseVo;
import com.mlming.springboot.vo.ShippingVo;
import com.mysql.cj.x.protobuf.MysqlxCrud;
import com.sun.org.apache.xpath.internal.operations.Or;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.client.protocol.Time;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private ShippingMapper shippingMapper;
    @Autowired
    private CartService cartService;
    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private OrderItemMapper orderItemMapper;

    @Autowired
    private RedissonClient redissonClient;// Redisson对象: 用于分布式锁的使用

    @Override
    @Transactional
    public ResponseVo<OrderVo> createOrder(Integer userId, Integer shippingId) {
        // 查询收货地址, 并对收货地址进行验证:
        Shipping shipping = shippingMapper.selectByPrimaryKey(shippingId);
        if(shipping == null) {
            return ResponseVo.error(ResponseEnum.SHIPPING_NOT_EXISTS);
        }
        // 查询购物车中被选中的商品,并进行验证
        List<Cart> carts = cartService.getCarts(userId).stream().filter(Cart::getProductSelected).collect(Collectors.toList());
        if(CollectionUtils.isEmpty(carts)) {
            return ResponseVo.error(ResponseEnum.CART_NOT_SELECTED_PRODUCT);
        }
        // 查询出商品的id集合, 由此查询被选择的商品的信息, 为了验证时, Cart遍历时,可以方便从products里面取值,所以可以将List转为Map
        Set<Integer> productIds = carts.stream().map(Cart::getProductId).collect(Collectors.toSet());

        // 对购物车中的每一个商品, 都要加一个分布式锁
        //  **只有当当前购物车里面每一个商品, 都可以被加锁成功时,购物车才算加锁成功**, 才能执行业务代码
        boolean isLock = true;
        Map<String,RLock> locks = new HashMap<>();// 把每个商品的锁存起来,方便之后释放
        for (Integer productId : productIds) {
            String curProductLockName = MallConst.PRODUCT_LOCK_NAME + productId;// 以 前缀标识 + 商品id作为锁名称
            RLock curProductLock = redissonClient.getLock(curProductLockName);
            boolean curIsLock = false;
            try {
                curIsLock = curProductLock.tryLock(10,3, TimeUnit.MINUTES);
                // 如果加锁成功, 就把当前的锁加到Map里面去
                if(curIsLock) locks.put(curProductLockName,curProductLock);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 购物车的锁 = 每一个商品的锁 的 &运算, 只要出现一个是false, 那么购物车就不会锁
            isLock = isLock & curIsLock;
        }
        // 如果购物车加锁成功, 就执行下面的业务代码, 从而阻止并发下的超卖问题:
        // 使用try-catch-finally括起来的原因:
        //      因为中间有很多次都会因为各种验证不通过, 而直接return
        //      那么此时就不会执行到 **释放锁** 那一步
        //      但是, 无论发生了什么, 都必须释放锁, 所以, 要加一个try-catch-finally,
        //      主要是**为了可以利用finally里面必然执行的性质, 从而保证释放锁**
        try {
            if(isLock) {
                // 再查一次redis, 如果此时该购物车已经被删除, 则不要再执行该购物车转订单的代码了
                carts = cartService.getCarts(userId).stream().filter(Cart::getProductSelected).collect(Collectors.toList());
                if(carts != null) {
                    // 从数据库中查询出购物车中所有商品的信息:
                    List<Product> productList = productMapper.selectByProductIdSet(productIds);
                    Map<Integer,Product> productMap = productList.stream().collect(Collectors.toMap(Product::getId,product -> product));
                    // 校验商品库存,状态等:
                    // 同时构建: OrderItemList, 用于存入数据库的orderItem表
                    List<OrderItem> orderItemList = new ArrayList<>();
                    // 订单号orderNo是要代码生成的唯一值
                    Long orderNo = generateOrderNo();
                    for (Cart cart : carts) {
                        Product product = productMap.getOrDefault(cart.getProductId(),null);
                        // 虽然productIds是从carts里面获得的, 但是数据库查询时使用的是in作为条件判断,所以不一定producIds里面有的,最终返回的productMap里面也有
                        if(product == null) {
                            return ResponseVo.error(ResponseEnum.PRODUCT_IN_CART_NOT_EXISTS,ResponseEnum.PRODUCT_IN_CART_NOT_EXISTS.getDesc()+":商品id为:"+cart.getProductId());
                        }
                        // 验证商品状态
                        if(!ProductStatusEnum.ON_SALE.getCode().equals(product.getStatus())) {
                            return ResponseVo.error(ResponseEnum.PRODUCT_OFF_SALE_OR_DELETE,ResponseEnum.PRODUCT_OFF_SALE_OR_DELETE.getDesc()+".商品是:"+product.getName());
                        }
                        // 验证商品库存
                        if(product.getStock() < cart.getQuantity()) {
                            return ResponseVo.error(ResponseEnum.PRODUCT_STOCK_NOT_ENOUGH,ResponseEnum.PRODUCT_STOCK_NOT_ENOUGH.getDesc()+".商品是:"+product.getName());
                        }
                        // 构建OrderItemVo
                        OrderItem orderItem = buildOrderItemVo(userId, orderNo, cart.getQuantity(), product);
                        orderItemList.add(orderItem);

                        // 减少库存,由事务保证与下面的操作一起成功
                        product.setStock(product.getStock() - cart.getQuantity());
                        int row = productMapper.updateByPrimaryKeySelective(product);
                        if(row <= 0) {
                            return ResponseVo.error(ResponseEnum.ERROR);
                        }
                    }
                    // 构建Order数据
                    Order order = buildOrder(orderNo,userId,shippingId,orderItemList);

                    // 往数据库写入Order与OrderItem
                    // 注意: 此时必须使用事务来保证两者的同时成功与失败
                    int rowForOrder = orderMapper.insertSelective(order);
                    if(rowForOrder <= 0) {
                        return ResponseVo.error(ResponseEnum.ERROR);
                    }
                    int rowForOrderItem = orderItemMapper.insertSelectiveByOrderItemList(orderItemList);
                    if(rowForOrderItem <= 0) {
                        return ResponseVo.error(ResponseEnum.ERROR);
                    }

                    // 更新Redis数据库: 把购物车的数据删除
                    for (Cart cart : carts) {
                        cartService.deleteCartItem(userId,cart.getProductId());
                    }
                    // 构建OrderVo作为返回
                    OrderVo orderVo = buildOrderVo(order,orderItemList,shipping);

                    return ResponseVo.success(orderVo);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 无论怎么样, 都要释放锁, 所以把释放锁的代码放到finally里面
            // 由于购物车的锁 = 每个商品的锁, 所以要一个个把商品的锁给释放了
            for (Integer productId : productIds) {
                locks.get(MallConst.PRODUCT_LOCK_NAME + productId).unlock();
            }
        }
        return ResponseVo.error(ResponseEnum.ERROR);
    }

    @Override
    public ResponseVo<PageInfo> getOrderList(Integer userId, Integer pageSize, Integer pageNum) {
        // 分页查询
        PageHelper.startPage(pageNum,pageSize);
        // 查询该用户的所有订单记录
        List<Order> orders = orderMapper.selectByUserId(userId);
        // 通过订单记录的OrderNo查询OrderItem
        // 由于order与orderItemList应该是对应的,一个订单号有一个订单有多个订单详情项
        // 所以应该转成Map<Long,List<OrderItem>>
        Set<Long> orderNoSet = orders.stream().map(Order::getOrderNo).collect(Collectors.toSet());
        List<OrderItem> orderItems = orderItemMapper.selectByOrderNoSet(orderNoSet);
        Map<Long,List<OrderItem>> orderItemMap = orderItems.stream().collect(Collectors.groupingBy(OrderItem::getOrderNo));
        // 通过订单记录的ShippingId查询Shipping
        // 因为方便取取货地址, 所以变为Map形式
        Set<Integer> shippingIdSet = orders.stream().map(Order::getShippingId).collect(Collectors.toSet());
        List<Shipping> shippings = shippingMapper.selectByIdSet(shippingIdSet);
        Map<Integer,Shipping> shippingMap = shippings.stream().collect(Collectors.toMap(Shipping::getId,shipping -> shipping));
        // 遍历orders,构造orderVoList
        List<OrderVo> orderVoList = new ArrayList<>();
        for (Order order : orders) {
            List<OrderItem> curOrderItemList = orderItemMap.getOrDefault(order.getOrderNo(),null);
            if(curOrderItemList == null) {
                return ResponseVo.error(ResponseEnum.ERROR);
            }
            Shipping curShipping = shippingMap.getOrDefault(order.getShippingId(), null);
            if(curShipping == null) {
                return ResponseVo.error(ResponseEnum.ERROR);
            }
            OrderVo orderVo = buildOrderVo(order, curOrderItemList, curShipping);
            orderVoList.add(orderVo);
        }
        // 构造PageInfo
        PageInfo pageInfo = new PageInfo(orders);
        pageInfo.setList(orderVoList);

        return ResponseVo.success(pageInfo);
    }

    @Override
    public ResponseVo<OrderVo> getOrderDetail(Integer userId, Long orderNo) {
        // 由于orderNo是唯一值, 所以通过orderNo查询, 然后验证一下userId是否是同一个即可
        Order order = orderMapper.selectByOrderNo(orderNo);
        if(!userId.equals(order.getUserId())) {
            return ResponseVo.error(ResponseEnum.ORDER_NOT_EXISTS);
        }
        // 获取orderItemList 以及 shipping
        Set<Long> orderNoSet = new HashSet<>();
        orderNoSet.add(orderNo);
        List<OrderItem> orderItemList = orderItemMapper.selectByOrderNoSet(orderNoSet);
        Shipping shipping = shippingMapper.selectByPrimaryKey(order.getShippingId());
        // 构造orderVo
        OrderVo orderVo = buildOrderVo(order, orderItemList, shipping);

        return ResponseVo.success(orderVo);
    }

    @Override
    public ResponseVo cancelOrder(Integer userId, Long orderNo) {
        // 由于orderNo是唯一值, 所以通过orderNo查询, 然后验证一下userId是否是同一个即可
        Order order = orderMapper.selectByOrderNo(orderNo);
        if(!userId.equals(order.getUserId())) {
            return ResponseVo.error(ResponseEnum.ORDER_NOT_EXISTS);
        }
        // 业务逻辑(根据具体业务去定): 只有[未支付]状态, 才能取消
        if(!PayStatusEnum.NOT_PAY.getCode().equals(order.getStatus())) {
            return ResponseVo.error(ResponseEnum.ORDER_STATUS_ERROR);
        }
        // 可以取消: 则把该order状态置于取消, 然后更新数据库
        order.setStatus(PayStatusEnum.CANCELED.getCode());
        int row = orderMapper.updateByPrimaryKeySelective(order);
        if(row <= 0) {
            return ResponseVo.error(ResponseEnum.ERROR);
        }

        return ResponseVo.success();
    }

    @Override
    public void paidOrder(Long orderNo) {
        // 查出该订单
        Order order = orderMapper.selectByOrderNo(orderNo);
        if(order == null) {
            throw new RuntimeException(ResponseVo.error(ResponseEnum.ERROR)+"订单号不存在:"+orderNo);
        }
        // 业务逻辑(根据具体业务去定): 只有[未支付]状态, 才能修改为[已支付]
        if(!PayStatusEnum.NOT_PAY.getCode().equals(order.getStatus())) {
            throw new RuntimeException(ResponseVo.error(ResponseEnum.ORDER_STATUS_ERROR).getMsg());
        }
        // 可以修改: 则把该order状态置于[已支付], 然后更新数据库
        order.setStatus(PayStatusEnum.PAYED.getCode());
        int row = orderMapper.updateByPrimaryKeySelective(order);
        if(row <= 0) {
            throw new RuntimeException(ResponseVo.error(ResponseEnum.ERROR).getMsg());
        }
    }

    private OrderVo buildOrderVo(Order order,List<OrderItem> orderItemList,Shipping shipping) {
        OrderVo orderVo = new OrderVo();
        BeanUtils.copyProperties(order,orderVo);
        List<OrderItemVo> orderItemVoList = new ArrayList<>();
        for (OrderItem orderItem : orderItemList) {
            OrderItemVo orderItemVo = new OrderItemVo();
            BeanUtils.copyProperties(orderItem,orderItemVo);
            orderItemVoList.add(orderItemVo);
        }
        orderVo.setOrderItemVoList(orderItemVoList);
        ShippingVo shippingVo = new ShippingVo();
        BeanUtils.copyProperties(shipping,shippingVo);
        orderVo.setShippingVo(shippingVo);
        return orderVo;
    }

    private Order buildOrder(Long orderNo,Integer userId,Integer shippingId,List<OrderItem> orderItemList) {
        BigDecimal payment = BigDecimal.ZERO;
        for (OrderItem orderItem : orderItemList) {
            payment = payment.add(orderItem.getTotalPrice());
        }
        Order order = new Order();
        order.setOrderNo(orderNo);
        order.setUserId(userId);
        order.setShippingId(shippingId);
        order.setPayment(payment);
        order.setPostage(0);// 运费,初定为0
        order.setPaymentType(PaymentTypeEnum.PAY_ONLINE.getCode());
        order.setStatus(PayStatusEnum.NOT_PAY.getCode());
        return order;
    }

    private Long generateOrderNo() {
        // 此处以一个时间戳+随机数组成
        // 一般企业级会采用分布式唯一id
        return System.currentTimeMillis() + new Random().nextInt(9999);
    }

    private OrderItem buildOrderItemVo(Integer userId, Long orderNo,Integer quantity,Product product) {
        OrderItem orderItem = new OrderItem();
        orderItem.setUserId(userId);
        orderItem.setOrderNo(orderNo);
        orderItem.setProductId(product.getId());
        orderItem.setQuantity(quantity);
        orderItem.setProductImage(product.getMainImage());
        orderItem.setTotalPrice(product.getPrice().multiply(BigDecimal.valueOf(quantity)));
        orderItem.setProductName(product.getName());
        orderItem.setCurrentUnitPrice(product.getPrice());
        return orderItem;
    }
}
